
<!DOCTYPE HTML>
<html>
<head>
	<meta charset="utf-8">
	<title>objective-c runtime 拾遗 - 乐の博客</title>
	<meta name="author" content="zhidong">

	
	<meta name="description" content="Objective-c Runtime 拾遗 关于OC runtime、消息转发网上已经有很多文章，这里就不重复了，只是将一下不常见的、容易遗漏的列一下。 动态添加类 我们通过objc_allocateClassPair、class_addIvar、class_addMethod、 &hellip;">
	<meta name="keywords" content="iOS,runtime">

  <!-- http://t.co/dKP3o1e -->
  <meta name="HandheldFriendly" content="True">
  <meta name="MobileOptimized" content="320">
  <meta name="viewport" content="width=device-width, initial-scale=1">

	<link href="/atom.xml" rel="alternate" title="乐の博客" type="application/atom+xml">
	
	<link rel="canonical" href="https://zhidong.rochu.cn/blog/2016/03/04/objc-runtime-pickup/">
	<link href="/favicon.png" rel="shortcut icon">
	<link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css">
	<!--[if lt IE 9]><script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script><![endif]-->
	<script src="/javascripts/libs/jquery.min.js"></script>
	<!-- <link href='//fonts.googleapis.com/css?family=Open+Sans:400italic,400,700' rel='stylesheet' type='text/css'> -->
	
</head>


<body>
	<div class="container">
		<div class="left-col">
			<div class="intrude-less">
			<header id="header" class="inner"><div class="profilepic">	
	<script src="/javascripts/md5.js"></script>
	<script type="text/javascript">
		document.write("<img src='/images/pingtouge.png' alt='Profile Picture' style='width: 160px;' />");
	</script>
</div>
<h1><a href="/">乐の博客</a></h1>
<p class="subtitle">行而不思则罔，思而不行则怠</p>
<nav id="main-nav"><ul>
	<li><a href="/">Blog</a></li>
	<li><a href="/about">About</a></li>
	<li><a href="/portfolio">Portfolio</a></li>
	<li><a href="/archives">Archive</a></li>
</ul>
</nav>
<nav id="sub-nav">
	<div class="social">
		
		<a class="weibo" href="http://www.weibo.com/agdsdl" title="Weibo">Weibo</a>
		
		
		
		
		
		<a class="github" href="https://github.com/agdsdl" title="GitHub">GitHub</a>
		
		
		
		
		
		
		
		<a class="rss" href="/atom.xml" title="RSS">RSS</a>
		
	</div>
</nav>
<p class="description">知冬的后花园</p>
</header>				
			</div>
		</div>	
		<div class="mid-col">
			
				
			
			<div class="mid-col-container">
				<div id="content" class="inner"><article class="post" itemscope itemtype="http://schema.org/BlogPosting">
	<h1 class="title" itemprop="name">Objective-c Runtime 拾遗</h1>
	<div class="entry-content" itemprop="articleBody"><p>关于OC runtime、消息转发网上已经有很多文章，这里就不重复了，只是将一下不常见的、容易遗漏的列一下。</p>

<h2>动态添加类</h2>

<p>我们通过<code>objc_allocateClassPair</code>、<code>class_addIvar</code>、<code>class_addMethod</code>、<code>objc_registerClassPair</code>来动态添加类。</p>

<p><code>objc_registerClassPair</code> 其中除了设置类状态，做的最重要的事是生成<code>ivar_layout</code>(在支持GC的情况下，所以ios中是没有这一步的)，<code>ivar_layout</code>保存了类strong变量的内存视图，runtime依赖他来管理strong变量。</p>

<p><code>ivar_layout</code>的结构<a href="!http://blog.sunnyxx.com/2015/09/13/class-ivar-layout/">阳神的博客里</a>有描述，这里就不重复了。</p>

<p><code>objc_registerClassPair</code>之后，类的<code>instanceSize</code>已经确定，这个新类已经可以投入使用，这时就不允许调用<code>class_addIvar</code>了。</p>

<p><code>class_addIvar</code>主要是对<code>ivar_list</code>链表的操作，并相应的增加<code>instanceSize</code>。</p>

<p>参考：
<a href="http://www.opensource.apple.com/tarballs/objc4/">Apple 源码</a></p>

<h2>动态方法解析</h2>

<p>一个OC方法的实现本质上就是一个简单的c函数，这个c函数至少要有self和_cmd两个参数。比如下面这个c函数：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>void dynamicMethodIMP(id self, SEL _cmd) {
</span><span class='line'>    // implementation ....
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>可以用<code>class_addMethod</code>来添加到现有的类中：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@implementation MyClass
</span><span class='line'>+ (BOOL)resolveInstanceMethod:(SEL)aSEL
</span><span class='line'>{
</span><span class='line'>    if (aSEL == @selector(resolveThisMethodDynamically)) {
</span><span class='line'>          class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
</span><span class='line'>          return YES;
</span><span class='line'>    }
</span><span class='line'>    return [super resolveInstanceMethod:aSEL];
</span><span class='line'>}
</span><span class='line'>@end</span></code></pre></td></tr></table></div></figure>


<p><code>resolveInstanceMethod</code>和<code>resolveClassMethod</code>可以用来动态提供一个方法实现。</p>

<p>OC runtime做消息转发（Message Forwarding）时，在调用<code>respondsToSelector:</code>和<code>instancesRespondToSelector:</code>之前，会先调用上面两个方法，让你有机会来动态添加方法实现。</p>

<p>OC支持一种动态属性允许你动态提供它的实现方法。</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@dynamic propertyName;</span></code></pre></td></tr></table></div></figure>


<p><a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtHowMessagingWorks.html">参考</a></p>

<!-- more -->


<h2>消息转发</h2>

<p>系统发送消息给一个对象时，如果这个对象不能响应这个消息，在报错之前，系统会调用这个对象的<code>forwardInvocation:</code> 让它有机会来处理这个消息。
顾名思义就是消息转发，通过将消息转发给另一个对象，使得一个对象可以模拟另外一个对象的能力：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>/*
</span><span class='line'>forwardInvocation 需要MethodSignature来创建NSInvocation对象，因此这个方法也要提供。
</span><span class='line'>*/
</span><span class='line'>- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector  
</span><span class='line'>{  
</span><span class='line'>    NSMethodSignature* signature = [super methodSignatureForSelector:aSelector];  
</span><span class='line'>    if (signature==nil) {  
</span><span class='line'>        signature = [someObj methodSignatureForSelector:aSelector];  
</span><span class='line'>    }  
</span><span class='line'>    NSUInteger argCount = [signature numberOfArguments];  
</span><span class='line'>    for (NSInteger i=0 ; i&lt;argCount ; i++) {  
</span><span class='line'>    }  
</span><span class='line'>      
</span><span class='line'>    return signature;  
</span><span class='line'>}  
</span><span class='line'>
</span><span class='line'>- (void)forwardInvocation:(NSInvocation *)anInvocation
</span><span class='line'>{
</span><span class='line'>    if ([someOtherObject respondsToSelector:
</span><span class='line'>            [anInvocation selector]])
</span><span class='line'>        [anInvocation invokeWithTarget:someOtherObject];
</span><span class='line'>    else
</span><span class='line'>        [super forwardInvocation:anInvocation];
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>


<p>你可以用这种机制来模拟多继承。</p>

<p>要真实的模拟多继承，你还必须重写这些方法：<code>respondsToSelector:</code> 、<code>isKindOfClass:</code>、<code>instancesRespondToSelector:</code>、如果继承对象中有协议，还需重写<code>conformsToProtocol:</code></p>

<h2>类型编码</h2>

<p>是OC runtime所依赖的重要类型信息，编译器将参数及返回类型编码成字符串，并与方法selector关联。</p>

<p>@encode是个编译器操作符，用来获取一个类型的编码。所有可以用sizeof操作符的类型，都可以用@encode来获取它的类型。</p>

<p>常见编码见<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html">官方文档</a></p>
</div>

</article>
<!-- 
	<div class="share">
	<div class="addthis_toolbox addthis_default_style addthis_16x16_style">
	
	
	
	
	
	<a class="addthis_button_compact"></a>
	<a class="addthis_counter addthis_bubble_style"></a>
	</div>
  <script type="text/javascript" src="http://s7.addthis.com/js/300/addthis_widget.js#pubid="></script>
</div>

 -->


<section id="comment">
    <h1 class="title">Comments</h1>
    <div id="gitalk-container"></div>
    <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
    <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
    <script>
    const gitalk = new Gitalk({
    clientID: '29bbf743e30b99394771',
    clientSecret: '41e4b7f1aa79742fc459f2971ad51596df5e4dda',
    repo: 'agdsdl.github.io',
    owner: 'agdsdl',
    admin: ['agdsdl'],
    id: location.pathname,      // Ensure uniqueness and length less than 50
    distractionFreeMode: false  // Facebook-like distraction free mode
    })

    gitalk.render('gitalk-container')
    </script>
</section></div>
			</div>
			<footer id="footer" class="inner">Copyright &copy; 2019

    zhidong

<br>
Powered by Octopress.

Design credit: <a href="http://shashankmehta.in/archive/2012/greyshade.html">Shashank Mehta</a></footer>
			<script src="/javascripts/slash.js"></script>
<!-- <script src="/javascripts/jquery.fancybox.pack.js"></script>
<script type="text/javascript">
(function($){
	$('.fancybox').fancybox();
})(jQuery);
</script> Delete or comment this line to disable Fancybox -->




	<script type="text/javascript">
		var _gaq = _gaq || [];
		_gaq.push(['_setAccount', 'UA-65560025-1']);
		_gaq.push(['_trackPageview']);

		(function() {
			var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
			ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
			var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
		})();
	</script>



		</div>
	</div>
</body>
</html>
